﻿#pragma once

#include "bind.h"
#include <algorithm>

namespace anip
{
	template<typename T>
	struct _tempBindable
		: public _tempBindableExpr<T>
	{
		const bindable<T>& source;
		_tempBindable(const bindable<T>& s) : source(s) {}
	};

	template<typename T>
	struct _tempBindableValue
		: public _tempBindableExpr<T>
	{
		const T value;
		_tempBindableValue(const T v) : value(v) {}
	};

	template<typename T>
	_tempBindable<T> expr(const bindable<T>& b)
	{
		return { b };
	}

	template<typename T>
	T _tempBindableEval(const _tempBindableExpr<T>& expr);
	// Do not implement

	template<typename T>
	std::set<const _bind_notifiable*> _tempBindableRefs(const _tempBindableExpr<T>& expr);
	// Do not implement

	template<typename T>
	T _tempBindableEval(const _tempBindable<T>& expr)
	{
		return expr.source;
	}

	template<typename T>
	std::set<const _bind_notifiable*> _tempBindableRefs(const _tempBindable<T>& expr)
	{
		return { dynamic_cast<const _bind_notifiable*>(&expr.source) };
	}

	template<typename T>
	T _tempBindableEval(const _tempBindableValue<T>& expr)
	{
		return expr.value;
	}

	template<typename T>
	std::set<const _bind_notifiable*> _tempBindableRefs(const _tempBindableValue<T>& expr)
	{
		return { };
	}

#define TEMP_BINDABLE_BINARY_OP_STRUCT(Op, Name)						\
	template<typename T1, typename T2>									\
	struct _tempBindable##Name											\
		: public _tempBindableExpr<decltype(							\
			std::declval<T1::ResultType>() Op std::declval<T2::ResultType>())>	\
	{																	\
		T1 first;														\
		T2 second;														\
		_tempBindable##Name(const T1& a, const T2& b)					\
			: first(a), second(b) {}									\
	};

#define TEMP_BINDABLE_BINARY_OP_A(Op, Name)								\
	template<typename T1, typename T2,									\
		typename R1 = typename T1::ResultType,							\
		typename R2 = typename T2::ResultType>							\
	_tempBindable##Name<T1, T2> operator Op(const T1& l, const T2& r)	\
	{ return { l, r }; }

#define TEMP_BINDABLE_BINARY_OP_B(Op, Name)								\
	template<typename T1, typename Tv,									\
		typename T2 = _tempBindableValue<Tv>,							\
		typename R1 = typename T1::ResultType,							\
		typename Check = decltype(std::declval<R1>() Op std::declval<Tv>()),		\
		std::enable_if_t<!std::is_base_of_v<_tempBindableRoot, Tv>, bool> = true> \
	_tempBindable##Name<T1, T2> operator Op(const T1& l, const Tv& r)   \
	{ return { l, _tempBindableValue<Tv>(r) }; }

#define TEMP_BINDABLE_BINARY_OP_C(Op, Name)								\
	template<typename T1, typename Tv,									\
		typename T2 = _tempBindableValue<Tv>,							\
		typename R1 = typename T1::ResultType,									\
		typename Check = decltype(std::declval<R1>() Op std::declval<Tv>()),		\
		std::enable_if_t<!std::is_base_of_v<_tempBindableRoot, Tv>, bool> = true> \
	_tempBindable##Name<T1, T2> operator Op(const Tv& l, const T1& r)	\
	{ return { _tempBindableValue<Tv>(l), r }; }

#define TEMP_BINDABLE_BINARY_OP_EVAL(Op, Name)							\
	template<typename T1, typename T2>									\
	/* constexpr */ decltype(auto) _tempBindableEval(					\
		const _tempBindable##Name<T1, T2>& expr)						\
	{ return _tempBindableEval(expr.first) Op _tempBindableEval(expr.second); }

#define TEMP_BINDABLE_BINARY_OP_REFS(Op, Name)			\
	template<typename T1, typename T2>					\
	std::set<const _bind_notifiable*> _tempBindableRefs(		\
		const _tempBindable##Name<T1, T2>& expr)		\
	{													\
		auto set1 = _tempBindableRefs(expr.first);		\
		auto set2 = _tempBindableRefs(expr.second);		\
		set1.insert(set2.begin(), set2.end());			\
		return set1;									\
	}

#define TEMP_BINDABLE_BINARY_OP(Op, Name)			\
		TEMP_BINDABLE_BINARY_OP_STRUCT(Op, Name);	\
		TEMP_BINDABLE_BINARY_OP_A(Op, Name);		\
		TEMP_BINDABLE_BINARY_OP_B(Op, Name);		\
		TEMP_BINDABLE_BINARY_OP_C(Op, Name);		\
		TEMP_BINDABLE_BINARY_OP_EVAL(Op, Name);		\
		TEMP_BINDABLE_BINARY_OP_REFS(Op, Name)

	TEMP_BINDABLE_BINARY_OP(+, Add);
	TEMP_BINDABLE_BINARY_OP(-, Sub);
	TEMP_BINDABLE_BINARY_OP(*, Mult);
	TEMP_BINDABLE_BINARY_OP(/, Div);

#undef TEMP_BINDABLE_BINARY_OP
#undef TEMP_BINDABLE_BINARY_OP_STRUCT
#undef TEMP_BINDABLE_BINARY_OP_A
#undef TEMP_BINDABLE_BINARY_OP_B
#undef TEMP_BINDABLE_BINARY_OP_C
#undef TEMP_BINDABLE_BINARY_OP_EVAL

#define TEMP_BINDABLE_FUNC_UNARY(Func, Name)									\
	template<typename T>														\
	struct _tempBindableFunc_##Name :											\
		public _tempBindableExpr<decltype(Func(std::declval<T::ResultType>()))>		\
	{ T term; _tempBindableFunc_##Name(const T& t) : term(t) {} };				\
	template<typename T, 														\
		typename R = typename T::ResultType>									\
	_tempBindableFunc_##Name<T> Name(const T& expr) { return { expr }; }		\
	template<typename T>														\
	decltype(auto) _tempBindableEval(const _tempBindableFunc_##Name<T>& expr)	\
		{ return Func(_tempBindableEval(expr.term)); }							\
	template<typename T>														\
	std::set<const _bind_notifiable*> _tempBindableRefs(								\
		const _tempBindableFunc_##Name<T>& expr)								\
		{ return _tempBindableRefs(expr.term); }

	TEMP_BINDABLE_FUNC_UNARY(std::sin, sin);
	TEMP_BINDABLE_FUNC_UNARY(std::cos, cos);
	TEMP_BINDABLE_FUNC_UNARY(std::tan, tan);
	TEMP_BINDABLE_FUNC_UNARY(std::sqrt, sqrt);

#undef TEMP_BINDABLE_FUNC_UNARY
}